/*
** To program the fuses:
**
** D:\>avrdude -c usbtiny -p atmega48 -t
** avrdude> write lfuse 0 0xe6 // osc to 20mhz
** avrdude> write hfuse 0 0xd7 // eesave turned on
** avrdude> q
** D:\>
**
** That will set the fuse for a 20mhz clock and turn
**  on the eesave option so that the eeprom will not
**  be overwritten when a new flash is uploaded.
**
** See Makefile for the current project fuse settings.
*/

#include <mvc8.h>

#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <avr/wdt.h>
#include <math.h>
#include <megaserial.h>
#include <serial.h>
#include <packet.h>
#include <crc8.h>
#include <touch.h>
#include "isradc.h"
#include "convertreading.h"

#define MVC803
//#define MVC806
//#define MVC808
//#define MVC812

uint8  i;
sint16 temperature;
volatile mvc8_ram_table_t ram;

/* * ** * * ** * * ** * * ** * * ** * * ** * * ** * * ** * * ** * * ** * * **
**
** Things to edit on this node:
**
** Persistent Data Table:
**
** uint8 NodeID[4]       ~ This node can listen to 4 network addresses.  By
**                          default the MVC nodes will respond to NodeID=1
**                          and also the broadcast address NodeID=100.  This
**                          allows all MVC nodes to respond to broadcast
**                          messages at address 100, such as ResetMinMax
**                          and so on.
**
** uint8 NumberVChannels ~ On the MVC8 there are 8 voltage channels 
**                          possible, and on the MVC4E there are 4 channels 
**                          possible but we  may want to read fewer than 
**                          that.
**
** uint8 NumberIChannels ~ If this unit is an MVC4E then 4 of the channels 
**                          are being used to read the interconnect wiring 
**                          losses.  We may want to read less than the 4 
**                          channels.
**
** uint8 NumberTChannels ~ This is the number of remote temperature sensors
**                          on the system.
**
** VCDef VTable[8]       ~ This is the table of Voltage Channels.  There 
**                          are up to 8 channels available depending on 
**                          how the module is built and how it is configured.
**
** ICDef ITable[4]       ~ This is the table of Interconnect Channels.  
**                          There are up to 4 channels available depending on
**                          how the module is built and how it is configured.
**
**
** Transient Data Table:
**
**
*/

#define NODEID 1

const uint8 moduleType  PROGMEM = 1;
const uint8 hardRev     PROGMEM = 6;
const uint8 softRev     PROGMEM = 1;

const uint8   nodeName[8]  EEMEM = "MVC8";
const uint32  nodeSerial   EEMEM = 1000;
//const uint8   nodeIDs[4]   EEMEM = { NODEID, 255, 255, 255 };
const uint8   nodeIDs[4]   EEMEM = { NODEID, 2, 3, 4 };
const uint8   nodePad[16]  EEMEM = { 255, 255 };

#ifdef MVC803
const range_t ranger[9]    EEMEM = 
{
//  { { -2730 }, { 7901 } },  // 0 (internal temp sensor -45..85C)
//  { { -4608 }, { 14542 } },  // 0 (internal temp sensor -49..185F)
//  { { -460 }, { 1454 } },  // 0 (internal temp sensor -49..185F)
//  { { 0 }, { 11000 } },  // 0 (internal temp sensor -49..185F)
  { { 0 }, { 1023 } },  // 0 (internal temp sensor -49..185F)
                            // 220 = -49F, 345 = 185F
                            // 220 = -45C, 345 = 85C (approx .1mv/count, read the docs!)
                            //   The internal temperature sensor needs to be tested and 
                            //    measured and calibrated on an individual unit basis.  
                            //    The MCU does not provide for accurate temperature
                            //    calibrations from the factory.
                            //
  { {  2400 }, {  4200 } }, // 1 (battery 1)
  { {  2400 }, {  4200 } }, // 2 (battery 2)
  { {  2400 }, {  4200 } }, // 3 (battery 3)
  { {  2400 }, {  4200 } }, // 4 (battery 4)
  { {  2400 }, {  4200 } }, // 5 (battery 5)
  { {  2400 }, {  4200 } }, // 6 (battery 6)
  { {  2400 }, {  4200 } }, // 7 (battery 7)
  { {  2400 }, {  4200 } }, // 8 (battery 8)
};

/*
** These are Factory Defaults.
**
*/
const range_t pranger[9] PROGMEM = 
{
  { { -4000 }, { 18500 } }, // 0 (internal temp sensor -40..185F)
  { {  2400 }, {  4200 } }, // 1 (battery 1)
  { {  2400 }, {  4200 } }, // 2 (battery 2)
  { {  2400 }, {  4200 } }, // 3 (battery 3)
  { {  2400 }, {  4200 } }, // 4 (battery 4)
  { {  2400 }, {  4200 } }, // 5 (battery 5)
  { {  2400 }, {  4200 } }, // 6 (battery 6)
  { {  2400 }, {  4200 } }, // 7 (battery 7)
  { {  2400 }, {  4200 } }, // 8 (battery 8)
};
#endif

#define eers16(x) ((sint16)eeprom_read_word( (uint16*)(x) ))
#define eeru8(x)  ((uint8)eeprom_read_byte(  (uint8*)(x) ))
#define pgru8(x)  ((uint8)pgm_read_byte_near( (uint8*)(x) ))
#define pgrs16(x) ((sint16)pgm_read_word(    (uint16*)(x) ))

#define BIT_LED  (1<<PORTD2)
#define BIT_DB1  (1<<PORTD6)
#define BIT_DB2  (1<<PORTD7)
#define BIT_REED (1<<PORTD5)

#define LED_ON  PORTD &= ~BIT_LED;
#define LED_OFF PORTD |=  BIT_LED;
#define LED_TOG PORTD ^=  BIT_LED;
#define LED_FLP LED_TOG LED_TOG

#define FLPDB1 PORTD ^= BIT_DB1
#define TOGDB1 FLPDB1; /* _delay_us(1); */ FLPDB1

#define FLPDB2 PORTD ^= BIT_DB2
#define TOGDB2 FLPDB2; /* _delay_us(1); */ FLPDB2

#define REED_IN ((PIND & BIT_REED) == 0)

/*************************************************************************/
/*************************************************************************/
/*************************************************************************/

uint8 NodeIDMatch(void)
{
  uint8 i;

  /*
  ** If the packet address matches one of our
  **  possible node addresses, then we respond
  **  to that also.
  **
  */
  for( i=0; i<4; i++ )
  {
    if( eeprom_read_byte(&nodeIDs[i]) == packet.dst )
      return( 1 );
  }

  /*
  ** If the packet destination is the
  **  broadcast address, then we respond to
  **  that.
  **
  */
  if( 255 == packet.dst )
    return( 1 );

  /*
  ** If the packet destination is the
  **  conditional broadcast address, then we
  **  see if our conditions are ready and 
  **  respond to that.
  **
  */
  if( 254 == packet.dst )
  {
    /*
    ** If the reed switch is low, then we respond
    **  to the conditional network address.
    **
    */
    if( REED_IN ) 
      return( 1 );

    /*
    ** If the touch network is shorted, then we
    **   respond to the conditional network address.
    **
    */
    if( touch_is_shorted() ) 
      return( 1 );

  } // endif( 254 == packet.dst )

  /*
  ** All mvc nodes respond to network address 100.
  **
  */
  if( 100 == packet.dst )
  {
    return(1);
  }

  /*
  ** If we cannot find a match above, then this
  **  packet was not addressed to us.
  **
  */
  return(0);

} // enduint8 NodeIDMatch(void)



/*************************************************************************/
/*************************************************************************/
/*************************************************************************/

adata_t adata[ISRADC_CHANNELS];

const uint8 admap[ISRADC_CHANNELS] PROGMEM =
//{ 8, 5, 3, 1, 7, 4, 2, 0, 6 };
{ 8, 5, 4, 3, 2, 1, 0, 7, 6 };

uint8 AdChannelMap( uint8 channel )
{
  return( pgru8(&admap[channel]) );
}

void init_ad(void)
{

  ADC_InitMinMax();

  /*
  ** Set up the A/D converter for conversions.
  **
  */
  DDRC   = 0x00; // all pins are analogs... we're not using them for anything else

  DIDR0  = 0b11111111;   // digital inputs disabled

  ADCSRB =
    (0b0    << ACME)   | // ADC not documented
    (0b000  << ADTS0)  ; // ADC Auto Trigger Source (000=free running)
                         //  000 = Free Running Mode
                         //  001 = Analog Comparator
                         //  010 = External Interrupt Request 0
                         //  011 = Timer/Counter0 Compare Match A
                         //  100 = Timer/Counter0 Overflow
                         //  101 = Timer/Counter1 Compare Match B
                         //  110 = Timer/Counter1 Overflow
                         //  111 = Timer/Counter Capture Event

  ADMUX =
    (0b11   << REFS0)  | // ADC Reference
                         //   0b00 ~ internal vref turned off
                         //   0b01 ~ AVCC with cap at aref pin
                         //   0b10 ~ reserved
                         //   0b11 ~ internal 1.1v reference with cap
                         //
    (0b0    << ADLAR)  | // ADC Left Adjust Result
                         //   0b0  ~ right adjust (10 bit)
                         //   0b1  ~ left adjust (8 bit)
    (0b0000 << MUX0)   ; // ADC Mux Channel Selector

  ADCSRA = 
    (0b1    << ADEN)   | // ADC Enable
    (0b1    << ADSC)   | // ADC Start Conversion (0 == not yet)
    (0b1    << ADATE)  | // ADC Auto Trigger Enable
    (0b0    << ADIF)   | // ADC Interrupt Flag
    (0b1    << ADIE)   | // ADC Interrupt Enable
    (0b111  << ADPS0)  ; // ADC Prescaler (max for 20Mhz)

} // endvoid init_ad()

//ISR(ADC_vect)
//{
//  uint8 i = adcc*8;
//
//  adata[0].raw = 0x55aa;
//  
//  adata[i].raw = ADCW;
//
//}

/*************************************************************************/
/*************************************************************************/
/*************************************************************************/

uint16 downCounter[4];

ISR(TIMER1_OVF_vect)
{
  if( downCounter[0] ) downCounter[0]--;
  if( downCounter[1] ) downCounter[1]--;
  if( downCounter[2] ) downCounter[2]--;
  if( downCounter[3] ) downCounter[3]--;
}

void init_timer(void)
{

  TCCR1A = 
    (0b00   << COM1A0) | // 6.Compare Output Mode for A
    (0b00   << COM1B0) | // 4.Compare Output Mode for B 
    (0b00   << WGM10)  ; // 0.Waveform Generation Mode

  TCCR1B = 
    (0b0    << ICNC1)  | // 7.Input Capture Noise Canceler
    (0b0    << ICES1)  | // 6.Input Capture Edge Select
    (0b00   << WGM12)  | // 3.Waveform Generation Mode
    (0b001  << CS10)   ; // 0.Clock Select (1=no prescaling)

  TCCR1C =
    (0b0    << FOC1A)  | // 7.Force Output Compare for A
    (0b0    << FOC1B)  ; // 6.Force Output Compare for B

  TIMSK1 = 
    (0b0    << ICIE1)  | // 5.Input Capture Interrupt Enable
    (0b0    << OCIE1B) | // 2.Output Compare B Match Interrupt Enable
    (0b0    << OCIE1A) | // 1.Output Compare A Match Interrupt Enable
    (0b1    << TOIE1)  ; // 0.Overflow Interrupt Enable

  OCR1AH = (20000>>8);
  OCR1AL = (20000&255);

  sei();

}



/*************************************************************************/
/*************************************************************************/
/*************************************************************************/

/*!
** \brief Convert A/D Reading
**
** This procedure takes a reading from the A/D conversion table
**  and converts it to some other scale range.  The scale range
**  is intended to make the A/D readings more meaningful.  Therefore,
**  A/D readings of 0..1023 may be converted to 2500..4200 (2.5v..4.2v).
**
** This procedure employs the 32-bit integer math pack.  This mathpack
**  is much faster than floating point, and also occupies much less
**  memory.  The A/D value that is being processed is shifted into
**  the 32-bit space where the divisions and multiplications occur.
**
**\note This routine has been optimized for the avr-mega series processor
**       that has only a 10-bit A/D.  It assumes the input low value
**       is always zero, and the input hi value is always 1023.  This
**       gives us a little bit of optimization in the code, but also
**       makes it less portable.  The original conversion lines are
**       commented out.
**
*/
uint16 ConvertAdReading( uint16 adin, uint8 i )
{
  /*
  ** Punk the watchdog.  This is a fairly intensive
  **  conversion routine, and we don't want to get
  **  into a jam if we spend too much time here.
  **
  */
  wdt_reset();

  /*
  ** Move the value into the 16-bit accumulator, subtract
  **  out the scaler.in.lo value, move it into the 32-bit
  **  accumulator and shift it up 12 bits.
  **
  ** Note: this routine *can* use an a/d reading of 
  **  something other than zero, but we are going to optimize
  **  this for the avr and not bother with the zero 
  **  subtraction.  Therefore, the first three lines are 
  **  commented off, and the a/d value is just taken directly.
  */
  s16  = adin;
  shiftIn();

  /*
  ** Calculate the span of the scaler.in and divide that
  **  into the A/D value (in 32-bit space).  This produces
  **  a 32-bit result that represents the signal percentage
  **  relative to the input scale.
  **
  ** Note: again, we can take the scale range of the input
  **  scaler, but since this is optimized for the avr a/d
  **  we are simply going to assume that the full scale is
  **  1023... which happens to be the upper most value of the
  **  10-bit a/d in the avr.  Therefore, the first three
  **  lines are commented off, and the 'span' of 1023 is used.
  **
  */
//  s16  = eers16(&scaler[i].i.hi.s16);
//  s16 -= eers16(&scaler[i].i.lo.s16);
//  s32 /= s16;
  s32 /= 1023; // (hi is always 1023)

  /*
  ** Calculate the span of the output scaler and multiply the
  **  input signal percentage into that.  Then, move out of
  **  32-bit space by dividing back down 12-bits (an easy and
  **  fast shift operation).
  **
  */
  s16  = eers16(&ranger[i].hi.s16);
  s16 -= eers16(&ranger[i].lo.s16);
  s32 *= s16;

  /*
  ** Move the new output value back into 16-bit space and
  **  add back in the output offset.
  **
  */
  shiftOut();
  s16 += eers16(&ranger[i].lo.s16);

  /*
  ** The caller gets a signed scaled reading.
  **
  */
  return( s16 );

} // enduint16 ConvertAdReading( uint16 adin, uint8 i )

/*************************************************************************/
/*************************************************************************/
/*************************************************************************/

/*!
** \brief Process System Amps
**
** This processes the system amps into a Kwh value for each cell.
**
*/
void ProcessVehicleInfo(void)
{

} // endvoid ProcessVehicleInfo(void)


/*************************************************************************/
/*************************************************************************/
/*************************************************************************/

void SendHeader(void)
{
  crc8Result = 0;
  txPut( 0, ':'        ); // atn
  txPut( 0, packet.src ); // dst (where we're sending to)
  txPut( 0, packet.dst ); // src (where it was sent to -us-)
  txPut( 0, ram.status ); // module status.
  txPut( 0, packet.cmd ); // cmd
}

void SendUint16( uint16 data )
{
  txPut( 0, data      );
  txPut( 0, data >> 8 );
}


uint8 ProcessNetworkCommand(void)
{
  uint8 i;
  uint8 forUs = 1;

  /*
  ** Fetch a packet from the network.  This packet
  **  needs to be addressed to us.  If this packet
  **  is not addressed to us, we are going to listen
  **  to it anyway since it contains vehicle info.
  **
  */
  if( !rxNetworkPacket() ) return(0); // forUs = 0;

  /*
  ** Most every packet contains vehicle info.  We
  **  will look for it and process it.
  **
  */
  ProcessVehicleInfo();

  /*
  ** Switch on the command sent to us.
  **
  */
  switch( packet.cmd )
  {
    /*
    ** Test
    **
    */
    case 250:
    {
      SendHeader();
      txPut( 0, 2          ); // len (how many bytes in data packet)
      txPut( 0, crc8Result ); // crc

      return( 1 );
    }

    /*
    ** Broadcast Vehicle Info.
    **
    ** This is a broadcast command that contains the vehicle amps, 
    **  temperature and other vehicle-wide data.  All units receive this 
    **  data packet, but don't make any response on it.  They just 
    **  internalize it as appropriate.
    **
    ** In the case of the battery voltage monitors, the units calculate
    **  the cell Kwh.
    **
    */
    case 240:
    {
      ProcessVehicleInfo();
      return( 1 );
    }

    /*
    ** Read raw A/D data
    **
    */
    case 20:
    {
      SendHeader();
      txPut( 0, ISRADC_CHANNELS*2 ); // len (how many bytes in data packet)

      /*
      ** Send 18 bytes of data packet.
      **
      */
      for( i=0; i<ISRADC_CHANNELS; i++ )
        SendUint16( adata[AdChannelMap(i)].raw );

      txPut( 0, crc8Result ); // crc

      return(1);
    }


    /*
    ** Read scaled A/D data
    **
    */
    case 21:
    {
      SendHeader();
      txPut( 0, ISRADC_CHANNELS*2 ); // len (how many bytes in data packet)

      /*
      ** Send 18 bytes of data packet.
      **
      */
      for( i=0; i<ISRADC_CHANNELS; i++ )
        SendUint16( ConvertAdReading(adata[AdChannelMap(i)].raw,i) );

      txPut( 0, crc8Result ); // crc

      return(1);
    }

    /*
    ** Read scaled A/D Average data
    **
    */
    case 22:
    {
      SendHeader();
      txPut( 0, ISRADC_CHANNELS*2 ); // len (how many bytes in data packet)

      /*
      ** Send 18 bytes of data packet.
      **
      */
      for( i=0; i<ISRADC_CHANNELS; i++ )
        SendUint16( ConvertAdReading(adata[AdChannelMap(i)].avg,i) );

      txPut( 0, crc8Result ); // crc

      return(1);
    }

    /*
    ** Read scaled A/D Average data
    **
    */
    case 23:
    {
      SendHeader();
      txPut( 0, ISRADC_CHANNELS*2 ); // len (how many bytes in data packet)

      /*
      ** Send 18 bytes of data packet.
      **
      */
      for( i=0; i<ISRADC_CHANNELS; i++ )
        SendUint16( ConvertAdReading(adata[AdChannelMap(i)].min,i) );

      txPut( 0, crc8Result ); // crc

      return(1);
    }

    /*
    ** Read scaled A/D Average data
    **
    */
    case 24:
    {
      SendHeader();
      txPut( 0, ISRADC_CHANNELS*2 ); // len (how many bytes in data packet)

      /*
      ** Send 18 bytes of data packet.
      **
      */
      for( i=0; i<ISRADC_CHANNELS; i++ )
        SendUint16( ConvertAdReading(adata[AdChannelMap(i)].max,i) );

      txPut( 0, crc8Result ); // crc

      return(1);
    }

    /*
    ** Reset min
    **
    */
    case 43:
    {
      for( i=0; i<ISRADC_CHANNELS; i++ )
        adata[i].min = 1024;

      return(1);
    }

    /*
    ** Reset max
    **
    */
    case 44:
    {
      for( i=0; i<ISRADC_CHANNELS; i++ )
        adata[i].max = 0;

      return(1);
    }

    /*
    ** Reset Kwh
    **
    */
    case 45:
    {
      return(1);
    }

  }

  return(0);

}



void readTemperature(void)
{
  if( touch_is_present() )
  {
    touch_write_byte(0xcc); // skip rom
    touch_write_byte(0xbe); // read scratch

    temperature  = touch_read_byte();
    temperature += touch_read_byte() << 8;

//    f  = temperature;
//    f /= 170;
//    f *= 153;
//    f += 32;
//    temperature  = f * 10;

    touch_is_present();
    touch_write_byte(0xcc); // skip rom
    touch_write_byte(0x44); // start conversion
    touch_set_high();           // hard pull-up (power supply)
  }
    
}

int main(void)
{
  temperature = 0;

  DDRD  |=  BIT_LED;  // output
  DDRD  |=  BIT_DB1;  // output
  DDRD  |=  BIT_DB2;  // output

  DDRD  &= ~BIT_REED; // input
  PORTD |=  BIT_REED; // pull-up

  /*
  ** Initialize the world
  **
  */
  init_timer();
  init_ad();
  init_usart( 0, 10 );
  touch_init();

  /*
  ** Let'r rip!
  **
  */
  ENABLE_INTERRUPTS;

  /*
  ** Main Control Loop
  **
  */
  forever
  {
    /*
    ** punk the watchdog
    **
    */
    wdt_reset();

    /*
    ** Respond to any network requests.
    **
    */
    ProcessNetworkCommand();

    /*
    ** Using one of the handy dandy timers, 
    **  read the external temperature sensors.
    **
    */
    if( !downCounter[0] )
    {
      readTemperature();
      downCounter[0] = 200;
    }

    /*
    ** Using one of the handy dandy timers,
    **  flash the LED.
    **
    */
    if( !downCounter[1] ) 
    {
      LED_TOG
      downCounter[1] = 100;
    }

//    _delay_ms(2);

#ifdef NEVER
    if( !downCounter[2] )
    {
      for( i=0; i<ISRADC_CHANNELS; i++ )
      {
        adata[i].raw = adsim;
        adata[i].avg = adsim;
        adata[i].min = adsim;
        adata[i].max = adsim;
      }
      
    
      adsim++;
      adsim &= 1023;
    
      downCounter[2] = 25;
    }
#endif    
      
  } // endforever

}

